#include <stdio.h>
#include <math.h>
#include <cutils/list.h>
#include <pthread.h>

#include "pwm.h"
#include "i2c.h"
#include "battery.h"

#define VOLTAGE_REFERENCE 3.3f

struct battery {
	uv_loop_t *loop;
	uv_timer_t monitor_timer;

	int8_t bus_type;
	int8_t bus_id;
	i2c_t i2c;
	pwm_t green_led;

	float voltage;

	//XXX: one loop, seems fine no lock
	//pthread_mutex_t lock;
	struct listnode cb_head;
};

struct battery_cb {
	struct listnode list;
	notify_cb cb;
	void *data;
};

struct battery *battery_create(uv_loop_t *loop, int8_t bus_type, int8_t bus_id)
{
	struct battery *thiz = NULL;
	char path[32];
	int32_t ret = 0;

	thiz = calloc(1, sizeof(struct battery));
	if (!thiz) {
		printf("Can't alloc mm\n");
		goto calloc_fail;
	}

	snprintf(path, sizeof(path), "/dev/i2c-%d", bus_id);
	ret = i2c_open(&thiz->i2c, path);
	if (ret < 0) {
		printf("i2c_open fail: %s\n", i2c_errmsg(&thiz->i2c));
		goto i2c_open_fail;
	}

	ret = pwm_open(&thiz->green_led, 0, 0);
	if (ret < 0) {
		printf("pwm_open fail: %s\n", pwm_errmsg(&thiz->green_led));
		goto pwm_open_fail;
	}

	ret = pwm_config(&thiz->green_led, 100000000, 50000000, 0);
	if (ret < 0)
		printf("pwm config fail: %s\n", pwm_errmsg(&thiz->green_led));

	thiz->monitor_timer.data = thiz;
	uv_timer_init(loop, &thiz->monitor_timer);

	thiz->loop = loop;
	thiz->bus_type = bus_type;
	thiz->bus_id = bus_id;

	//pthread_mutex_init(&thiz->lock, NULL);
	list_init(&thiz->cb_head);

	return thiz;

pwm_open_fail:
	i2c_close(&thiz->i2c);
i2c_open_fail:
	free(thiz);
calloc_fail:
	return NULL;
}

int32_t battery_register_callback(struct battery *thiz, notify_cb cb, void *context)
{
	struct battery_cb *bcb = NULL;
	int32_t ret = 0;

	if (!thiz)
		return -EINVAL;

	bcb = calloc(1, sizeof(struct battery_cb));
	if (!bcb) {
		printf("Can't alloc mm\n");
		ret = -ENOMEM;
		goto calloc_fail;
	}

	bcb->cb = cb;
	bcb->data = context;

	//pthread_mutex_lock(&thiz->lock);
	list_add_tail(&thiz->cb_head, &bcb->list);
	//pthread_mutex_unlock(&thiz->lock);

	return 0;
calloc_fail:
	return ret;
}

void battery_unregiser_callback(struct battery *thiz, void *context)
{
	struct listnode *node;

	if (!thiz)
		return;

	list_for_each(node, &thiz->cb_head) {
		struct battery_cb *cb = node_to_item(node, struct battery_cb, list);
		if (cb->data == context) {
			list_remove(node);
			free(cb);
			return;
		}
	}
}

static void calc(uv_timer_t *handle)
{
	int ret;
	float vol = 0.0;
	struct listnode *node;
	struct battery *thiz;
	__u8 cmd[] = {0x01};
	__u16 adc;
	struct i2c_msg msgs[2];

	if (!handle)
		return;

	thiz = handle->data;

	msgs[0].addr = 0x68;
	msgs[0].flags = 0;
	msgs[0].len = sizeof(cmd);
	msgs[0].buf = cmd;

	msgs[1].addr = 0x68;
	msgs[1].flags = I2C_M_RD;
	msgs[1].len = sizeof(adc);
	msgs[1].buf = (__u8 *)&adc;

	/* XXX: Weired: split read into two transfer */
	ret = i2c_transfer(&thiz->i2c, msgs, 1);
	if (ret < 0) {
		printf("Error i2c transaction: %s\n", i2c_errmsg(&thiz->i2c));
		return;
	}

	ret = i2c_transfer(&thiz->i2c, msgs + 1, 1);
	if (ret < 0) {
		printf("Error i2c read: %s\n", i2c_errmsg(&thiz->i2c));
		return;
	}

	//low pass filter
	vol = floorf(((adc / 4095.0) * VOLTAGE_REFERENCE) * 1000) / 1000;
	thiz->voltage = 0.65 * (vol - thiz->voltage) + thiz->voltage;

	//notify_cb
	list_for_each(node, &thiz->cb_head) {
		struct battery_cb *cb = node_to_item(node, struct battery_cb, list);
		cb->cb(cb->data, vol);
	}

	printf("adc: %d, vol: %f\n", adc, vol);
	return;
}

//XXX: Only safe when exit loop?
void battery_destroy(struct battery *thiz)
{
	struct listnode *item;

	if (!thiz)
		return;

	battery_stop_monitor(thiz);

	//pthread_mutex_lock(&thiz->lock);
	while (!list_empty(&thiz->cb_head)) {
		item = list_head(&thiz->cb_head);
		list_remove(item);
		free(node_to_item(item, struct battery_cb, list));
	}
	//pthread_mutex_unlock(&thiz->lock);

	i2c_close(&thiz->i2c);
	free(thiz);
}

int32_t battery_start_monitor(struct battery *thiz)
{
	if (!thiz)
		return -EINVAL;

	uv_timer_start(&thiz->monitor_timer, calc, 200, 200);
	return 0;
}

int32_t battery_stop_monitor(struct battery *thiz)
{
	if (!thiz)
		return -EINVAL;

	uv_timer_stop(&thiz->monitor_timer);
	return 0;
}
